mutable struct SimpleBlock <: Block
    M
    steady_state_options
    solve_steady_state_options
    impulse_nonlinear_options
    solve_impulse_nonlinear_options
    impulse_linear_options
    jacobian_options
    partial_jacobians_options

    f
    name
    inputs
    outputs

    function SimpleBlock(f::Function)
        _M = Bijection(Dict())
        _steady_state_options = Dict()
        _solve_steady_state_options = Dict{String, Any}(
            "solver" => "",
            "solver_kwargs" => Dict(),
            "ttol" => 1e-12,
            "ctol" => 1e-9,
            "verbose" => false,
            "constrained_method" => "linear_continuation",
            "constrained_kwargs" => Dict())
        _impulse_nonlinear_options = Dict()
        _solve_impulse_nonlinear_options = Dict(
            "tol" => 1e-8,
            "maxit" => 30,
            "verbose" => true)
        _impulse_linear_options = Dict()
        _jacobian_options = Dict()
        _partial_jacobians_options = Dict()

        f_ext = ExtendedFunction(f)

        return new(_M,
                    _steady_state_options,
                    _solve_steady_state_options,
                    _impulse_nonlinear_options,
                    _solve_impulse_nonlinear_options,
                    _impulse_linear_options,
                    _jacobian_options,
                    _partial_jacobians_options,
                    f_ext,
                    f_ext.name,
                    f_ext.inputs,
                    f_ext.outputs)
    end
end

function simple(f)
    return SimpleBlock(f)
end

Base.show(io::IO, b::SimpleBlock) = print(io, "<SimpleBlock '$(b.name)'>")

function _steady_state(b::SimpleBlock, ss)
    outputs = wrapped_call(b.f, ss; preprocess=ignore, postprocess=numeric_primitive)
    return SteadyStateDict(Dict(ss..., outputs...))
end

function _impulse_nonlinear(b::SimpleBlock, ss, inputs; outputs, ss_initial)
    if isnothing(ss_initial)
        ss_initial = ss
        ss_initial_flag = false
    else
        ss_initial_flag = true
    end

    input_args = Dict()
    for (k, v) ∈ inputs
        if v isa Real
            error("Keyword argument $k = $v is scalar, should be time path.")
        end
        input_args[k] = Displace(v .+ ss[k]; ss=ss[k], ss_initial=ss_initial[k], name=k)
    end

    for k ∈ b.inputs
        if k ∉ keys(input_args)
            if !ss_initial_flag || (ss_initial_flag && ss_initial[k] == ss[k])
                input_args[k] = ignore(ss[k])
            else
                input_args[k] = Displace(fill(ss[k], inputs.T); ss=ss[k], ss_initial=ss_initial[k], name=k)
            end
        end
    end
    return ImpulseDict(make_impulse_uniform_length(b.f(input_args)))[outputs] - ss
end

function _impulse_linear(b::SimpleBlock, ss, inputs, outputs, Js)
end

function _jacobian(b::SimpleBlock, ss, inputs, outputs; T)
    invertedJ = Dict(i => Dict() for i ∈ inputs)

    for i ∈ inputs
        invertedJ[i] = compute_single_shock_J(b, ss, i)
    end

    J = Dict(o => Dict() for o ∈ outputs)

    for o ∈ outputs
        for i ∈ inputs
            if !isempty(invertedJ[i][o]) && !iszero(invertedJ[i][o])
                J[o][i] = invertedJ[i][o]
            end
        end
    end

    return JacobianDict(J; outputs=outputs, inputs=inputs, name=b.name, T=T)
end

function compute_single_shock_J(b::SimpleBlock, ss, i)
    input_args = Dict{AbstractString, Union{Ignore, AccumulatedDerivative}}(i => ignore(ss[i]) for i ∈ b.inputs)
    input_args[i] = AccumulatedDerivative(f_value=ss[i])

    J = Dict{AbstractString, Union{Dict, SimpleSparse}}(o => Dict() for o ∈ b.outputs)
    for (o_name, o) in b.f(input_args)
        if o isa AccumulatedDerivative
            J[o_name] = SimpleSparse(o.elements)
        end
    end

    return J
end

function make_impulse_uniform_length(out)
    T = maximum([length(v) for v in values(out)])
    return Dict(k => v isa Real ? fill(numeric_primitive(v), T) : numeric_primitive(v) for (k, v) in out)
end
